package com.ds.request;

import lombok.Getter;

import org.apache.tomcat.util.http.fileupload.IOUtils;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
 * 自定义request包装类:可重用的主体请求包装器
 * 由于采用采用application/json传输参数时，HttpServletRequest只能读取一次 body 中的内容。
 * 因为是读的字节流，读完就没了，因此需要需要做特殊处理。为实现述多次读取 Request 中的 Body 内容，
 * 需继承 HttpServletRequestWrapper 类，
 * 读取 Body 的内容，然后缓存到 byte[] 中；
 * 这样就可以实现多次读取 Body 的内容了。
 *
 * @author huahua
 * @DATE 2025/5/4
 **/
public class ReusableBodyRequestWrapper extends HttpServletRequestWrapper {
    /**
     * 参数字节数组，用于存储请求体的字节数据
     */
    @Getter
    private byte[] requestBody;

    /**
     * Http请求对象
     */
    private HttpServletRequest request;

    /**
     * 构造函数，初始化包装类
     *
     * @param request 原始HttpServletRequest对象
     * @throws IOException 如果读取请求体时发生IO错误
     */
    public ReusableBodyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.request = request;
    }

    /**
     * 重写getInputStream方法，实现请求体的重复读取
     *
     * @return 包含请求体数据的ServletInputStream对象
     * @throws IOException 如果读取请求体时发生IO错误
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        /**
         * 每次调用此方法时将数据流中的数据读取出来，然后再回填到InputStream之中
         * 解决通过@RequestBody和@RequestParam（POST方式）读取一次后控制器拿不到参数问题
         */
        // 仅当requestBody未初始化时，从请求中读取并存储到requestBody
        if (null == this.requestBody) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            IOUtils.copy(request.getInputStream(), baos);
            this.requestBody = baos.toByteArray();
        }

        // 创建一个 ByteArrayInputStream 对象，用于重复读取requestBody
        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
        return new ServletInputStream() {

            @Override
            public boolean isFinished() {
                // 始终返回false，表示数据流未完成
                return false;
            }

            @Override
            public boolean isReady() {
                // 始终返回false，表示数据流未准备好
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {
                // 不执行任何操作，因为该数据流不支持异步操作
            }

            @Override
            public int read() {
                //从ByteArrayInputStream中读取数据
                return bais.read();
            }
        };
    }

    /**
     * 重写getReader方法，返回一个基于getInputStream的BufferedReader
     *
     * @return 包含请求体数据的BufferedReader对象
     * @throws IOException 如果读取请求体时发生IO错误
     */
    @Override
    public BufferedReader getReader() throws IOException {
        // 基于getInputStream创建BufferedReader
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
}

